package com.dream.file;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;

import com.dream.file.chunk.ChunkUploadResult;
import com.dream.file.chunk.UploadChunkRepository;
import com.dream.file.chunk.UploadTaskRepository;

import lombok.extern.slf4j.Slf4j;

/**
 * 本地文件存储实现
 * 基于本地文件系统实现文件存储、上传、下载等功能
 */
@Slf4j
public class LocalFileStorage implements FileStorage {

    /**
     * 存储配置信息
     */
    private final StorageConfig config;
    
    /**
     * 上传任务仓库
     */
    private final UploadTaskRepository uploadTaskRepository;
    
    /**
     * 上传分片仓库
     */
    private final UploadChunkRepository uploadChunkRepository;
    
    /**
     * 构造方法
     * 
     * @param config 存储配置
     * @param uploadTaskRepository 上传任务仓库
     * @param uploadChunkRepository 上传分片仓库
     */
    public LocalFileStorage(StorageConfig config,
                            UploadTaskRepository uploadTaskRepository,
                            UploadChunkRepository uploadChunkRepository) {
        this.config = config;
        this.uploadTaskRepository = uploadTaskRepository;
        this.uploadChunkRepository = uploadChunkRepository;
        // 确保存储根目录存在
        createBaseDirectory();
    }

    /**
     * 创建基础存储目录
     */
    private void createBaseDirectory() {
        try {
            Path basePath = Paths.get(config.getBasePath());
            if (!Files.exists(basePath)) {
                Files.createDirectories(basePath);
                log.info("创建本地存储根目录: {}", basePath);
            }
        } catch (Exception e) {
            throw new FileStorageException("创建存储根目录失败", e);
        }
    }

    /**
     * 上传文件
     * 
     * @param inputStream 文件输入流
     * @param path 存储路径
     * @param fileName 文件名
     * @return 访问URL
     */
    @Override
    public String upload(InputStream inputStream, String path, String fileName) {
        try {
            Path fullPath = getFullPath(path);
            // 确保目录存在
            if (!Files.exists(fullPath)) {
                Files.createDirectories(fullPath);
            }
            
            Path targetFile = fullPath.resolve(fileName);
            // 使用Files.copy复制输入流到目标文件
            Files.copy(inputStream, targetFile, StandardCopyOption.REPLACE_EXISTING);
            
            log.info("文件上传成功: {}", targetFile);
            return generateUrl(path, fileName);
        } catch (Exception e) {
            throw new FileStorageException("文件上传失败", e);
        }
    }

    /**
     * 分片上传文件
     * 支持大文件的分片上传，可以实现断点续传
     * 
     * @param inputStream 文件输入流
     * @param path 存储路径
     * @param fileName 文件名
     * @param chunkNumber 当前分片序号
     * @param totalChunks 总分片数
     * @return 分片上传结果
     */
    @Override
    public ChunkUploadResult uploadChunk(InputStream inputStream, String path, String fileName,
                                         int chunkNumber, int totalChunks) {
        try {
            // 获取或创建上传任务ID
            String uploadId = getOrCreateMultipartUploadId(path, fileName);
            Path fullPath = getFullPath(path);
            
            // 确保目录存在
            if (!Files.exists(fullPath)) {
                Files.createDirectories(fullPath);
            }
            
            // 临时分片文件路径
            Path chunkFilePath = fullPath.resolve(getChunkFileName(fileName, uploadId, chunkNumber));
            
            // 保存分片
            Files.copy(inputStream, chunkFilePath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
            
            // 创建结果对象
            ChunkUploadResult result = new ChunkUploadResult();
            result.setUploadId(uploadId);
            result.setEtag(calculateEtag(chunkFilePath)); // 简单的ETag实现
            result.setPartNumber(chunkNumber);
            result.setCompleted(chunkNumber == totalChunks);
            
            // 如果是最后一个分片，执行合并操作
            if (result.isCompleted()) {
                completeMultipartUpload(path, fileName, uploadId, totalChunks);
            }
            
            return result;
        } catch (Exception e) {
            throw new FileStorageException("分片上传失败", e);
        }
    }

    /**
     * 下载文件
     * 
     * @param path 文件路径
     * @param fileName 文件名
     * @return 文件输入流
     */
    @Override
    public InputStream download(String path, String fileName) {
        try {
            Path filePath = getFullPath(path).resolve(fileName);
            if (!Files.exists(filePath)) {
                throw new FileStorageException("文件不存在: " + filePath);
            }
            return Files.newInputStream(filePath);
        } catch (Exception e) {
            throw new FileStorageException("文件下载失败", e);
        }
    }

    /**
     * 范围下载文件
     * 支持断点续传下载，指定下载文件的部分内容
     * 
     * @param path 文件路径
     * @param fileName 文件名
     * @param start 开始位置（字节偏移量）
     * @param end 结束位置（字节偏移量）
     * @return 文件部分内容的输入流
     */
    @Override
    public InputStream downloadRange(String path, String fileName, long start, long end) {
        try {
            Path filePath = getFullPath(path).resolve(fileName);
            if (!Files.exists(filePath)) {
                throw new FileStorageException("文件不存在: " + filePath);
            }
            
            // 使用RandomAccessFile实现范围读取
            RandomAccessFile randomAccessFile = new RandomAccessFile(filePath.toFile(), "r");
            randomAccessFile.seek(start);
            
            // 读取指定范围的数据
            byte[] buffer = new byte[(int) (end - start + 1)];
            randomAccessFile.readFully(buffer);
            randomAccessFile.close();
            
            return new ByteArrayInputStream(buffer);
        } catch (Exception e) {
            throw new FileStorageException("范围下载失败", e);
        }
    }

    /**
     * 删除文件
     * 
     * @param path 文件路径
     * @param fileName 文件名
     * @return 是否删除成功
     */
    @Override
    public boolean delete(String path, String fileName) {
        try {
            Path filePath = getFullPath(path).resolve(fileName);
            if (Files.exists(filePath)) {
                Files.delete(filePath);
                log.info("文件删除成功: {}", filePath);
                return true;
            }
            return false;
        } catch (Exception e) {
            log.error("文件删除失败", e);
            return false;
        }
    }

    /**
     * 获取文件元数据信息
     * 
     * @param path 文件路径
     * @param fileName 文件名
     * @return 文件元数据
     */
    @Override
    public FileMetadata getMetadata(String path, String fileName) {
        try {
            Path filePath = getFullPath(path).resolve(fileName);
            if (!Files.exists(filePath)) {
                throw new FileStorageException("文件不存在: " + filePath);
            }
            
            BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);
            
            FileMetadata metadata = new FileMetadata();
            metadata.setFileName(fileName);
            metadata.setPath(path);
            metadata.setSize(attrs.size());
            metadata.setContentType(getContentType(fileName));
            metadata.setLastModified(attrs.lastModifiedTime().toInstant());
            metadata.setEtag(calculateEtag(filePath));
            
            // 本地文件系统没有标准的元数据存储，可以实现一个简单的方案
            // 例如：为每个文件创建一个对应的.meta文件存储元数据
            Map<String, String> metaMap = readMetadataFile(filePath);
            metadata.setMetadata(metaMap);
            
            return metadata;
        } catch (Exception e) {
            throw new FileStorageException("获取文件元数据失败", e);
        }
    }

    /**
     * 获取完整的文件路径
     * 
     * @param relativePath 相对路径
     * @return 完整的文件系统路径
     */
    private Path getFullPath(String relativePath) {
        String basePath = StringUtils.trimToEmpty(config.getBasePath());
        relativePath = StringUtils.trimToEmpty(relativePath);
        return Paths.get(basePath, relativePath);
    }

    /**
     * 生成访问URL
     * 
     * @param path 文件路径
     * @param fileName 文件名
     * @return 访问URL
     */
    private String generateUrl(String path, String fileName) {
        // 本地文件系统实现可以返回相对路径或映射到HTTP服务的路径
        // 假设有一个HTTP服务映射到文件存储目录
        String endpoint = StringUtils.trimToEmpty(config.getEndpoint());
        if (endpoint.endsWith("/")) {
            endpoint = endpoint.substring(0, endpoint.length() - 1);
        }
        
        path = StringUtils.trimToEmpty(path);
        if (!path.startsWith("/")) {
            path = "/" + path;
        }
        if (!path.endsWith("/")) {
            path = path + "/";
        }
        
        return endpoint + path + fileName;
    }

    /**
     * 获取文件内容类型
     * 
     * @param fileName 文件名
     * @return 内容类型/MIME类型
     */
    private String getContentType(String fileName) {
        // 简单实现，实际项目中可以使用更完善的方法
        String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
        return switch (ext) {
            case "jpg", "jpeg" -> "image/jpeg";
            case "png" -> "image/png";
            case "pdf" -> "application/pdf";
            default -> "application/octet-stream";
        };
    }

    /**
     * 获取或创建分片上传ID
     * 
     * @param path 存储路径
     * @param fileName 文件名
     * @return 上传ID
     */
    private String getOrCreateMultipartUploadId(String path, String fileName) {
        // 实际项目中应该将上传ID持久化到数据库
        // 这里简单使用UUID
        return UUID.randomUUID().toString();
    }

    /**
     * 获取分片文件名
     * 
     * @param fileName 原始文件名
     * @param uploadId 上传ID
     * @param chunkNumber 分片序号
     * @return 分片文件名
     */
    private String getChunkFileName(String fileName, String uploadId, int chunkNumber) {
        return fileName + "." + uploadId + ".part" + chunkNumber;
    }

    /**
     * 计算文件的ETag值
     * 
     * @param filePath 文件路径
     * @return ETag值
     */
    private String calculateEtag(Path filePath) throws IOException {
        // 简单实现，使用文件最后修改时间和大小
        BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);
        return attrs.lastModifiedTime().toMillis() + "-" + attrs.size();
    }

    /**
     * 完成分片上传，合并文件
     * 
     * @param path 存储路径
     * @param fileName 文件名
     * @param uploadId 上传ID
     * @param totalChunks 总分片数
     */
    private void completeMultipartUpload(String path, String fileName, String uploadId, int totalChunks) throws IOException {
        Path fullPath = getFullPath(path);
        Path targetFile = fullPath.resolve(fileName);
        
        // 以追加方式创建目标文件
        try (FileChannel targetChannel = FileChannel.open(targetFile, 
                StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
            
            // 按顺序合并所有分片
            for (int i = 1; i <= totalChunks; i++) {
                Path chunkPath = fullPath.resolve(getChunkFileName(fileName, uploadId, i));
                if (!Files.exists(chunkPath)) {
                    throw new FileStorageException("分片文件不存在: " + chunkPath);
                }
                
                // 将分片内容追加到目标文件
                try (FileChannel chunkChannel = FileChannel.open(chunkPath, StandardOpenOption.READ)) {
                    chunkChannel.transferTo(0, chunkChannel.size(), targetChannel);
                }
                
                // 删除分片文件
                Files.delete(chunkPath);
            }
        }
        
        log.info("分片合并完成: {}", targetFile);
    }

    /**
     * 读取文件的元数据信息
     * 
     * @param filePath 文件路径
     * @return 元数据映射
     */
    private Map<String, String> readMetadataFile(Path filePath) {
        // 简单实现：检查是否存在同名的.meta文件
        Path metaPath = Paths.get(filePath.toString() + ".meta");
        Map<String, String> metaMap = new HashMap<>();
        
        if (Files.exists(metaPath)) {
            try {
                List<String> lines = Files.readAllLines(metaPath);
                for (String line : lines) {
                    String[] parts = line.split("=", 2);
                    if (parts.length == 2) {
                        metaMap.put(parts[0].trim(), parts[1].trim());
                    }
                }
            } catch (Exception e) {
                log.error("读取元数据文件失败", e);
            }
        }
        
        return metaMap;
    }
} 